﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;

namespace System {

    public class AppDomainTypeFinder:ITypeFinder {
        #region Fields

        private bool _ignoreReflectionErrors = true;
        #endregion

        #region Ctor

        public AppDomainTypeFinder() {
     
        }

        #endregion

        #region Utilities
        private void AddAssembliesInAppDomain(List<string> addedAssemblyNames,List<Assembly> assemblies) {
            foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies()) {
                if(!Matches(assembly.FullName))
                    continue;

                if(addedAssemblyNames.Contains(assembly.FullName))
                    continue;

                assemblies.Add(assembly);
                addedAssemblyNames.Add(assembly.FullName);
            }
        }
        protected virtual void AddConfiguredAssemblies(List<string> addedAssemblyNames,List<Assembly> assemblies) {
            foreach(var assemblyName in AssemblyNames) {
                var assembly = Assembly.Load(assemblyName);
                if(addedAssemblyNames.Contains(assembly.FullName))
                    continue;

                assemblies.Add(assembly);
                addedAssemblyNames.Add(assembly.FullName);
            }
        }
        protected virtual bool Matches(string assemblyFullName) {
            return !Matches(assemblyFullName,AssemblySkipLoadingPattern)
                   &&Matches(assemblyFullName,AssemblyRestrictToLoadingPattern);
        }
        protected virtual bool Matches(string assemblyFullName,string pattern) {
            return Regex.IsMatch(assemblyFullName,pattern,RegexOptions.IgnoreCase|RegexOptions.Compiled);
        }
        protected virtual void LoadMatchingAssemblies(string directoryPath) {
            var loadedAssemblyNames = new List<string>();

            foreach(var a in GetAssemblies()) {
                loadedAssemblyNames.Add(a.FullName);
            }
            
            if(!Directory.Exists(directoryPath)) {
                return;
            }

            foreach(var dllPath in GetFiles(directoryPath,"*.dll")) {
                try {
                    var an = AssemblyName.GetAssemblyName(dllPath);
                    if(Matches(an.FullName)&&!loadedAssemblyNames.Contains(an.FullName)) {
                        App.Load(an);
                    }
                } catch(BadImageFormatException ex) {
                    Trace.TraceError(ex.ToString());
                }
            }
        }

        public virtual string[] GetFiles(string directoryPath,string searchPattern = "",bool topDirectoryOnly = true) {
            if(string.IsNullOrEmpty(searchPattern))
                searchPattern="*.*";

            return Directory.GetFileSystemEntries(directoryPath,searchPattern,
                new EnumerationOptions {
                    IgnoreInaccessible=true,
                    MatchCasing=MatchCasing.CaseInsensitive,
                    RecurseSubdirectories=!topDirectoryOnly,

                });
        }
        protected virtual bool DoesTypeImplementOpenGeneric(Type type,Type openGeneric) {
            try {
                var genericTypeDefinition = openGeneric.GetGenericTypeDefinition();
                foreach(var implementedInterface in type.FindInterfaces((objType,objCriteria) => true,null)) {
                    if(!implementedInterface.IsGenericType)
                        continue;

                    if(genericTypeDefinition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition()))
                        return true;
                }

                return false;
            } catch {
                return false;
            }
        }
        protected virtual IEnumerable<Type> FindClassesOfType(Type assignTypeFrom,IEnumerable<Assembly> assemblies,bool onlyConcreteClasses = true) {
            var result = new List<Type>();
            try {
                foreach(var a in assemblies) {
                    Type[] types = null;
                    try {
                        types=a.GetTypes();
                    } catch {
                        if(!_ignoreReflectionErrors) {
                            throw;
                        }
                    }

                    if(types==null)
                        continue;

                    foreach(var t in types) {
                        if(!assignTypeFrom.IsAssignableFrom(t)&&(!assignTypeFrom.IsGenericTypeDefinition||!DoesTypeImplementOpenGeneric(t,assignTypeFrom)))
                            continue;

                        if(t.IsInterface)
                            continue;

                        if(onlyConcreteClasses) {
                            if(t.IsClass&&!t.IsAbstract) {
                                result.Add(t);
                            }
                        } else {
                            result.Add(t);
                        }
                    }
                }
            } catch(ReflectionTypeLoadException ex) {
                var msg = string.Empty;
                foreach(var e in ex.LoaderExceptions)
                    msg+=e.Message+Environment.NewLine;

                var fail = new Exception(msg,ex);
                Debug.WriteLine(fail.Message,fail);

                throw fail;
            }

            return result;
        }

        #endregion

        #region Methods
        public IEnumerable<Type> FindClassesOfType<T>(bool onlyConcreteClasses = true) {
            return FindClassesOfType(typeof(T),onlyConcreteClasses);
        }
        public IEnumerable<Type> FindClassesOfType(Type assignTypeFrom,bool onlyConcreteClasses = true) {
            return FindClassesOfType(assignTypeFrom,GetAssemblies(),onlyConcreteClasses);
        }
        public virtual IList<Assembly> GetAssemblies() {
            var addedAssemblyNames = new List<string>();
            var assemblies = new List<Assembly>();

            if(LoadAppDomainAssemblies)
                AddAssembliesInAppDomain(addedAssemblyNames,assemblies);
            AddConfiguredAssemblies(addedAssemblyNames,assemblies);

            return assemblies;
        }

        #endregion

        #region Properties

        public virtual AppDomain App => AppDomain.CurrentDomain;


        public bool LoadAppDomainAssemblies { get; set; } = true;


        public IList<string> AssemblyNames { get; set; } = new List<string>();


        public string AssemblySkipLoadingPattern { get; set; } = "^System|^mscorlib|^Microsoft|^AjaxControlToolkit|^Antlr3|^Autofac|^AutoMapper|^Castle|^ComponentArt|^CppCodeProvider|^DotNetOpenAuth|^EntityFramework|^EPPlus|^FluentValidation|^ImageResizer|^itextsharp|^log4net|^MaxMind|^MbUnit|^MiniProfiler|^Mono.Math|^MvcContrib|^Newtonsoft|^NHibernate|^nunit|^Org.Mentalis|^PerlRegex|^QuickGraph|^Recaptcha|^Remotion|^RestSharp|^Rhino|^Telerik|^Iesi|^TestDriven|^TestFu|^UserAgentStringLibrary|^VJSharpCodeProvider|^WebActivator|^WebDev|^WebGrease";
        public string AssemblyRestrictToLoadingPattern { get; set; } = ".*";

        #endregion
    }
}
